package vooga.entities;

import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Dimension2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;

import vooga.behaviors.AbstractBehavior;
import vooga.core.Layer;
import vooga.geom.DoubleDimension;

/**
 * Simple shape that knows its position, size, color, and velocity.
 * Also can have a z-order and belong to a set of groups.  The set
 * of groups should not be changed while the Entity is being managed by an EntitySet
 */
public class Entity
{
	private Point2D myCenter;
    private Dimension2D mySize;
    private java.awt.Color myColor;
    private double myHeading;
    private boolean isVisible;
    private boolean isDestructible;
    private java.awt.Shape myRepresentation;
    private vooga.geom.Vector myVelocity;
    private java.util.Map<String, AbstractBehavior> myBehaviors;
    private int myZOrder;
    private java.util.Set<String> myGroups;

    /**
     * 
     * @param topLeft top left coordinate of entity
     * @param size size of entity
     * @param graphic graphical representation of entity of size 1.0 x 1.0
     */
    public Entity(Point2D topLeft, Dimension2D size, Shape graphic)
    {
        myCenter = new Point2D.Double(topLeft.getX() + size.getWidth() / 2, topLeft.getY() + size.getHeight() / 2);
        mySize = (Dimension2D)size.clone();
        myRepresentation = graphic;
        
        myZOrder = Layer.DEFAULT_Z_ORDER;
        
        myGroups = new java.util.TreeSet<String>();
      	
        myBehaviors = new java.util.HashMap<String, AbstractBehavior>();
        setColor(java.awt.Color.BLACK);
        setHeading(0);
        isVisible = true;
        myVelocity = new vooga.geom.Vector(0,0);
    }

    
    public Entity(Rectangle2D bounds, Shape graphic)
    {
    	this(new Point2D.Double(bounds.getMinX(), bounds.getMinY()),
    		new DoubleDimension(bounds.getWidth(), bounds.getHeight()),
    		graphic);
    }
    
    public vooga.geom.Vector getVelocity()
    {
    	return myVelocity;
    }
    
    public void setVelocity(vooga.geom.Vector v)
    {
    	myVelocity = v;
    }
    
    public double getLeft ()
    {
        return getCenterX() - getWidth() / 2;
    }

    public double getTop ()
    {
        return getCenterY() - getHeight() / 2;
    }

    public double getRight ()
    {
        return getCenterX() + getWidth() / 2;
    }

    public double getBottom ()
    {
        return getCenterY() + getHeight() / 2;
    }

    public Point2D getTopLeft()
    {
	return new Point2D.Double(getLeft(), getTop());
    }

    public Dimension2D getSize ()
    {
    	return (Dimension2D)mySize.clone();
    }

    public double getHeading ()
    {
	return Math.toDegrees(myHeading);
    }

    public boolean isVisible ()
    {
        return isVisible;
    }
    
    public boolean isDestructible()
    {
    	return isDestructible;
    }

    public double getCenterX()
    {
    	return getCenter().getX();
    }

    public double getCenterY()
    {
    	return getCenter().getY();
    }

    public Point2D getCenter()
    {
    	return (Point2D)myCenter.clone();
    }

    
    /**
     * recenter the entity to another point 
     * @param center new center of the bounds of this entity
     */
    public void setCenter (Point2D center)
    {
    	myCenter = (Point2D)center.clone();
    }

    public void setSize (Dimension2D size)
    {
    	mySize.setSize(size);
    }

    public java.awt.Color getColor ()
    {
        return myColor;
    }

    public void setColor (java.awt.Color color)
    {
        myColor = color;
    }

    public void setHeading (double degrees)
    {
        myHeading = Math.toRadians(degrees);
    }

    public void setDestructible(boolean destruct)
    {
    	isDestructible = destruct;
    }
    
    public void translate (double dx, double dy)
    {
        myCenter.setLocation(getCenterX() + dx, getCenterY() + dy);
    }

    public void scale (double xFactor, double yFactor)
    {
        mySize.setSize(getWidth(), getHeight());
    }

    public void rotate (double degrees)
    {
        myHeading += Math.toRadians(degrees);
    }
    
    public void addBehavior(AbstractBehavior b)
    {
        myBehaviors.put(b.getClass().getName(), b);
    }

    public AbstractBehavior getBehavior(AbstractBehavior b)
    {
        return myBehaviors.get(b.getClass().getName());
    }

    public AbstractBehavior getBehavior(Class b)
    {
        return myBehaviors.get(b.getName());
    }

    public void removeBehavior (AbstractBehavior b)
    {
        myBehaviors.remove(b.getClass().getName());
    }

    public AbstractBehavior removeBehavior(Class b)
    {
        return myBehaviors.remove(b.getName());
    }

    public void act()
    {
        for (AbstractBehavior current : myBehaviors.values())
        {
            current.step(this);
        }
    }
    
    public void paint (java.awt.Graphics2D layer)
    {
        if (isVisible())
        {
            render(layer);
        }
    }

    protected void render (java.awt.Graphics2D layer)
    {
        layer.setColor(getColor());
        layer.fill(getShape());
    }

    protected java.awt.geom.AffineTransform getTransform ()
    {
        java.awt.geom.AffineTransform result = new java.awt.geom.AffineTransform();

        result.translate(getCenterX(),  getCenterY());
        result.rotate(myHeading);
        result.scale(getWidth(), getHeight());
        result.translate(-0.5, -0.5);

        return result;
    }

    public void setVisible (boolean visibility)
    {
        isVisible = visibility;
    }

    protected Shape getShape ()
    {
        GeneralPath result = new GeneralPath(myRepresentation);
        result.transform(getTransform());
        return result;
    }

    public boolean intersects (Entity other)
    {
        // more expensive and more precise
        java.awt.geom.Area result = new java.awt.geom.Area(getShape());
        result.intersect(new java.awt.geom.Area(other.getShape()));
        return ! result.isEmpty();
    }

    public boolean contains (Point2D point)
    {
        // more expensive and more precise
        Area result = new Area(getShape());
        return result.contains(point);
    }

    public void setTop(double y) {
		translate(0, y - getTop());
	}    
    
    public void setBottom(double y) {
		translate(0 , y - getBottom());
	}
    
    public double getHeight() {
		return getSize().getHeight();
	}

	public void setLeft(double x) {
		translate(x - getLeft(), 0);
	}    
    
    public void setRight(double x) {
		translate(x - getRight(), 0);
	}

    public double getWidth() {
		return getSize().getWidth();
	}

    /**
     * 
     * @return the groups that this Entity belongs to
     */
	public java.util.Set<String> getGroups() {
		return myGroups;
	}

	public void removeGroup(String g) {
		myGroups.remove(g);
	}

	public void addGroup(String g) {
		myGroups.add(g);
	}

	public void setGroups(Collection<String> g) {
		myGroups.clear();
		addGroups(g);
	}
	
	public void removeGroups(Collection<String> g) {
		myGroups.removeAll(g);
	}

	public void addGroups(Collection<String> g) {
		myGroups.addAll(g);
	}
	
	public int getZOrder() {
		return myZOrder;
	}

	public void setZOrder(int zOrder) {
		myZOrder = zOrder;
	}
    
}
